package com.even.camerarecoder

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build
import com.evendai.loglibrary.Timber
import java.nio.ByteBuffer


@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class H264Encoder(producer: NgnProxyVideoProducer, isPortrait: Boolean) {
    private val mMediaCodec: MediaCodec
    private var inputBufferIndex: Int = MediaCodec.INFO_TRY_AGAIN_LATER
    private var outputBufferIndex: Int = MediaCodec.INFO_TRY_AGAIN_LATER
    private lateinit var inputBuffer: ByteBuffer
    private lateinit var outputBuffer: ByteBuffer
    @Suppress("SpellCheckingInspection")
    private var mBufferInfo = MediaCodec.BufferInfo()

    private var startTime = 0L
    private var receiveFrameCount = 0
    @Suppress("PrivatePropertyName")
    private val _1K = 1000
    @Suppress("PrivatePropertyName")
    private val _1M = _1K * 1000
    private val mH264ToMp4SaverThread = H264ToMp4SaverThread.newInstance()


    private var muxerDataIndex = -1
    private var muxerDataCache: Array<MuxerData>? = null
    private var flags = 0
    /** 表示已经调用了close()方法 */
    private var calledCloseMethod = false
    /** 表示已经调用了releaseMediaCodec()方法 */
    private var calledReleaseMediaCodecMethod = false

    init {
        val bitrate = getBitrate(producer.videoWidth, producer.videoHeight) /*producer.videoWidth * producer.videoHeight*/
        Timber.i("width = ${producer.videoWidth}, height = ${producer.videoHeight}, bitrate = ${bitrate / 1024f / 1024f}M")
        mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
        // mMediaCodec = MediaCodec.createByCodecName("OMX.google.h264.encoder") // 指定使用软编码器
        // MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) 这样获取的H264编码器为：OMX.qcom.video.encoder.avc
        // OMX.qcom.video.encoder.avc   是硬件编码器，支持NV12
        // OMX.google.h264.encoder      是软件编码器，支持NV12、I420

        // 当视频旋转90度或270度时，此处的宽高要调换位置
        val mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,
                if (isPortrait) producer.videoWidth else producer.videoHeight,
                if (isPortrait) producer.videoHeight else producer.videoWidth)

        /*
        球机的编码器支持的颜色格式：
        COLOR_FormatYUV420SemiPlanar（NV12）  十进制：21
        COLOR_FormatYUV420Planar（I420）      十进制：19
        上面两种是过时的，都推荐用：COLOR_FormatYUV420Flexible，明明是两种不同格式，不知道为什么推荐用的是同一个
        COLOR_FormatYUV420Flexible  十进制：2135033992，十六进制：7F420888
        COLOR_FormatSurface  十进制2130708361，十六进制： 7F000789
        */
//        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, if (NgnApplication.isBall()) MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible else MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar)
        @Suppress("DEPRECATION")
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar)

        // 注：不能使用静态码率，否则视频画面会有不停的间隔地出现微模糊效果。虽然使用了动态码率，但是码率值还是要设置的，如果不设置在调用mMediaCodec.configure()时会报异常
        // 动态码率：真实码率会是我们设置的码率的上下范围浮动,码率会根据视频画面变化而实时变化。
        mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) // 设置码率为动态码率，默认也是动态码率
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate) // 码率（即比特率）, 官方Demo这里的1000即1kbps，1000_000即1mpbs
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25)      // 帧速（25帧/秒）
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) // I帧间隔（1帧/2秒），因为帧 1秒出25帧，2秒就出50帧，所以I帧间隔为2的话就是每50帧出一个关键帧
        //MediaFormat.KEY_ROTATION: 描述输出到output surface上所需的顺时针旋转的键。 仅当使用output surface配置编解码器时才使用此键。
        // 关联值为整数，代表度。支持的值是0、90、180或270。如果未指定，则旋转默认为0。
        //mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 90)

        Timber.i("mediaFormat = $mediaFormat")

        // 第二个参数用于显示解码器的视频内容，第三个参数为编解码器的解密参数，第四个参数为指定为编码器
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

        mH264ToMp4SaverThread.start()
        mMediaCodec.start()

        H264Util.printSupportedH264Encoder()
        Timber.i("实际使用的H264编码器：${mMediaCodec.codecInfo.name}")
    }

    /**
     * 把NV12格式的Yuv数据编码为H264数据，此方法执行完就是编码完一帧的数据
     */
    @SuppressLint("SwitchIntDef")
    @Synchronized
    fun encodeYuvToH264(@Suppress("SpellCheckingInspection") yuvDatas: ByteArray) {
        if (calledReleaseMediaCodecMethod) return // 如果已经释放了编码器，则不处理，因为多线程，所以有可能释放的时候还有数据扔进来编码的
        // 把YUV数据放入MediaCodec中进行H264编码
        inputBufferIndex = mMediaCodec.dequeueInputBuffer(10_000) // 如果10毫秒都等不到可用缓冲，则这一帧的yuv数据将丢掉。谷歌官方Demo也是用的这个值
        @Suppress("SpellCheckingInspection")
        if (inputBufferIndex >= 0) {
            inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex)!!
            inputBuffer.put(yuvDatas) // 官方Demo在调用put之前会先调用inputBuffer.clear()，实际上并不需要
            // if (!isRuning) flag = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
            // 参数presentationTimeUs：指定我们这一帧数据应该在什么时间显示，单位为微秒，System.nanoTime()得到是当前时间的表示的纳秒。1000纳秒等于1微秒
            // 谷歌官方Demo计算方式为： presentationTimeUs = frameIndex * 1000000(1秒对应的微秒值） / fps。frameIndex从0开始
            // 比如fps为10帧/秒，则第1帧应该在0.0秒时显示，第2帧应该在0.1秒时显示，第3帧应该在0.2秒时显示，当然这些都要换成参数需要的微秒单位
            // 我发现presentationTimeUs不需要从0开始，只要把接收到帧数据时的当前时间传给它即可
            mMediaCodec.queueInputBuffer(
                inputBufferIndex,
                0,
                yuvDatas.size,
                System.nanoTime() / 1000, // 设置此帧视频在什么时间显示，单位为微秒，1毫秒 = 1000微秒
                flags // 官方Demo默认也是用0，结束时用MediaCodec.BUFFER_FLAG_END_OF_STREAM
            )
        }

        // 如果已经没有数据了，可以调用mMediaCodec.signalEndOfInputStream()来发送一个达到流结尾的信号，但是不调用也没有任何影响

        // 从MediaCodec中取出编好的H264数据并使用（如保存、发送）
        outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10_000)
        if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // 注：这里拿到的MediaFormat才是有效的，才可以给MediaMuxer使用
            //CodecData.videoMediaFormat = mMediaCodec.getOutputFormat(outputBufferIndex)
            mediaFormat = mMediaCodec.outputFormat
        }
        while (outputBufferIndex >= 0) {
            /* 索引为负数时有这些值：
                    MediaCodec.INFO_TRY_AGAIN_LATER：-1       // 尚无可用输出，稍后再试（即在mMediaCodec.dequeueOutputBuffer中指定了超时，而且这个超时时间到了还没有可用输出则会返回这个值，
                                                                 此时不应该再循环取缓冲输出,因为已经没有数据可取了，应该跳出循环，开始等待新数据的输入）
                    MediaCodec.INFO_OUTPUT_FORMAT_CHANGED：-2 // 应该在接收缓冲区之前发生（也就是还没有进行编码工作之前发生），并且应该只发生一次。输出格式已更改，后续数据将采用新格式。 getOutputFormat（）返回新格式。
                                                                请注意，您还可以使用新的getOutputFormat（int）方法来获取特定输出缓冲区的格式。这使您不必跟踪输出格式更改。
                    MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED：-3 // 表示输出的缓冲区已经改变了，需要使用getOutputBuffers()来获取新的缓冲区，
                                                                此外，此事件表示视频缩放模式可能已重置为默认值。而这个函数在Api21中已经弃用，
                                                                所以我们在使用新的方式获取缓冲数据时可以忽略这个返回值的情况。
                                                                现在我们是使用getOutputBuffer(index)来获取缓冲输出，而不是用getOutputBuffers()来获取缓冲区
             */
            outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex)!!
            when (mBufferInfo.flags) {
                0, MediaCodec.BUFFER_FLAG_KEY_FRAME -> {  // 普通帧、关键帧
                    // 保存mp4时，不要在关键前面插入配置数据，否则会出现花屏
                    mH264ToMp4SaverThread.addH264Data(getMuxerData(outputBuffer, mBufferInfo))
                }
                MediaCodec.BUFFER_FLAG_END_OF_STREAM -> {
                    // TODO 官方写法：mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0
                    // TODO 实际打印mBufferInfo.flags的值就是4，与BUFFER_FLAG_END_OF_STREAM相等，搞清楚为什么官方要这么写
                    Timber.e("已经到达流的终点了, mBufferInfo.flags = ${mBufferInfo.flags}")
                    mMediaCodec.releaseOutputBuffer(outputBufferIndex, false)
                    releaseMediaCodec()
                    break // 退出循环，无需再去获取编码的数据，肯定没有数据了。
                }
            }

            mMediaCodec.releaseOutputBuffer(outputBufferIndex, false) // 把缓存对象还给MediaCodec
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10_000)
        }

        receiveFrameCount++
        if (System.currentTimeMillis() - startTime >= 10_000) {
            Timber.e("H264Encoder每10秒编码${receiveFrameCount}帧")
            startTime = System.currentTimeMillis()
            receiveFrameCount = 0
        }
    }

    /** 使用此方法创建MuxerData，以实现对象复用 */
    private fun getMuxerData(byteBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo): MuxerData {
        if (muxerDataCache == null) {
            muxerDataCache = Array(cacheSize) {
                MuxerData(true, byteBuffer.capacity())
            }
        }

        if (++muxerDataIndex >= cacheSize) {
            muxerDataIndex = 0
        }

        return muxerDataCache!![muxerDataIndex].apply {
            copyFrom(byteBuffer, bufferInfo)
        }
    }

    private fun getBitrate(videoWidth: Int, videoHeight: Int): Int = when {
        (videoWidth == 1920 && videoHeight == 1080) || (videoWidth == 1080 && videoHeight == 1920) -> _1M * 3 shr 1
        (videoWidth == 1280 && videoHeight == 720) || (videoWidth == 720 && videoHeight == 1280) -> _1M
        (videoWidth == 640 && videoHeight == 480) || (videoWidth == 480 && videoHeight == 640) -> _1K * 500
        (videoWidth == 352 && videoHeight == 288) || (videoWidth == 288 && videoHeight == 352) -> _1K * 300
        else -> _1M * 1
        /*(videoWidth == 1920 && videoHeight == 1080) || (videoWidth == 1080 && videoHeight == 1920) -> _1M * 3 shr 1
        (videoWidth == 1280 && videoHeight == 720) || (videoWidth == 720 && videoHeight == 1280) -> _1M * 2
        (videoWidth == 640 && videoHeight == 480) || (videoWidth == 480 && videoHeight == 640) -> _1M
        (videoWidth == 352 && videoHeight == 288) || (videoWidth == 288 && videoHeight == 352) -> _1K * 500
        else -> _1M * 1*/
    }

    /**
     * 关闭编码器，因为一个线程进行编码的时候，另一个线程调用关闭函数，所以需要添加同步
     */
    @Synchronized
    fun close() {
        Timber.i("close")
        if (calledCloseMethod) return // 预防关闭函数被调用两次
        calledCloseMethod = true
        flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM
        encodeYuvToH264(ByteArray(0))
    }

    private fun releaseMediaCodec() {
        calledReleaseMediaCodecMethod = true

        try {
            mMediaCodec.stop()
        } catch (e: Exception) {
            Timber.e(e, "，别慌，正常停止${H264Encoder::class.java.simpleName}时出现的异常！")
        }

        try {
            mMediaCodec.release()
        } catch (e: Exception) {
            Timber.e(e, "，别慌，正常释放${H264Encoder::class.java.simpleName}时出现的异常！")
        }

        try {
            mH264ToMp4SaverThread.close()
        } catch (e: Exception) {
            Timber.fe(e, "关闭${H264ToMp4SaverThread::class.java.simpleName}时出现的异常！")
        }

        mediaFormat = null
        muxerDataCache = null
        muxerDataIndex = -1
    }

    companion object {
        //private val BIT_RATE: Int = 0 // videoWidth * videoHeight * 3 * 8 * FRAME_RATE / COMPRESS_RATIO;
        //private val COMPRESS_RATIO = 256
        var mediaFormat: MediaFormat? = null
        var cacheSize = 3
    }

}
